﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Drawing;
using System.Diagnostics;

namespace EpiMapit
{
    public class CaseSetKmlGenerator
    {

        private XDocument doc;
        private XElement document;
        private RingGenerator ring;
        private Description description;
        private bool usePins;
        private HashSet<LatLonDate> locationsFoundAlready;
        private Dictionary<Color, int> colorToStyleNumber;

        public CaseSetKmlGenerator(RingGenerator ringGenerator, Description description, bool usePins)
        {
            this.ring = ringGenerator;
            this.description = description;
            this.usePins = usePins;
            this.locationsFoundAlready = new HashSet<LatLonDate>();

            doc = new XDocument();

            XNamespace ns = "http://www.opengis.net/kml/2.2";
            var kml = new XElement(ns + "kml");
            doc.Add(kml);

            document = new XElement("Document",
                new XElement("name", "EpiMapit Viz"),
				new XElement("description", DateTimeOffset.Now.ToString() + " - " + Environment.MachineName));

            // Add style now and later make reference to them to reduce KML size
            colorToStyleNumber = new Dictionary<Color, int>();
            for (int i = 0; i < ControlConstants.RingColors.Length; i++)
            {
                var style = new XElement("Style",
                            new XElement("LineStyle",
                                new XElement("color", ControlConstants.CircleBorderColor)),
                            new XElement("PolyStyle",
                                new XElement("color", ControlConstants.RingColors[i].ToKml())));
                style.SetAttributeValue(XName.Get("id"), "style" + i);
                document.Add(style);

                colorToStyleNumber[ControlConstants.RingColors[i]] = i;
            }

            kml.Add(document);
        }

        public XDocument Document
        {
            get { return doc; }
        }

        public void Add(CaseSetGroup caseSetGroup, Visibility visibility, int levelOfDetail )
        {
            if (caseSetGroup.Count == 0)
                return;

            var folder = new XElement("Folder",
							new XElement("name", 
                                string.Format("{0:0.##}", caseSetGroup.MaxDistance) + " Km on " +
                                caseSetGroup.First().Date.ToShortDateString()));            

            foreach (var caseSet in caseSetGroup)
            {
                var figures = ring.GenerateFor(caseSet, caseSetGroup.Unit, levelOfDetail);
                var boundingBox = figures.GetBoundingBox();

                if (figures.Count() == 0)
                    continue;

                var caseSetDate = caseSet.Date.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
                var descriptionResult = description(caseSet, caseSetGroup);

                foreach (var figure in figures)
                {
                    var placemark = new XElement("Placemark",
                                        new XElement("name", string.Join(" - ", caseSet.RegionCodes.ToArray()))
                    );

                    if (!usePins)
                        placemark.Add(new XElement("description", new XCData(descriptionResult)));

                    placemark.Add(new XElement("styleUrl", "#style" + colorToStyleNumber[figure.Color]));

                    if (caseSet.HasDate)
                        placemark.Add(new XElement("TimeStamp", new XElement("when", caseSetDate)));

                    folder.Add(placemark);

                    bool addRegion = visibility != Visibility.Always && usePins;
                    if (addRegion)
                    {
                        XElement region = new XElement("Region", boundingBox.Kml);
                        switch(visibility) {
                            case Visibility.FirstLevel:
                                region.Add(GetLod(ControlConstants.LodMinPixels, -1));
                                break;
                            case Visibility.LastLevel:
                                region.Add(GetLod(-1, ControlConstants.LodMaxPixels));
                                break;
                            case Visibility.InBetweenLevel:
                                region.Add(GetLod(ControlConstants.LodMinPixels, ControlConstants.LodMaxPixels));
                                break;
                            default:
                                throw new ArgumentException("Unknown visibility: " + visibility); 
                        }
                        placemark.Add(region);
                    }

                    placemark.Add(figure.Kml);                    
                }

                if (!usePins)
                    continue;

                // This is so only one pin exists per location
                LatLonDate location = new LatLonDate(caseSet.Lat, caseSet.Lon, caseSet.Date);
                if (locationsFoundAlready.Contains(location))
                    continue;

                // Add a pin with the chart data.
                var pin = new XElement("Placemark", new XElement("name", string.Join(" - ", caseSet.RegionCodes.ToArray())));
                if (caseSet.HasDate)
                    pin.Add(new XElement("TimeStamp", new XElement("when", caseSetDate)));
                var point = new XElement("Point");
                point.Add(new XElement("coordinates", String.Format("{0},{1}", caseSet.Lon, caseSet.Lat)));
                pin.Add(point);
                pin.Add(new XElement("description", new XCData(descriptionResult)));
                folder.Add(pin);

                locationsFoundAlready.Add(location);
            }
            
			document.Add(folder);
        }

        private string CreateChartString(string title, string chartUrl)
        {
            string ret = string.Format(@"<b>{0}:</b><img src=""{1}""></img>", title, chartUrl);
            return ret;
        }

        private XElement GetLod(int min, int max)
        {
            var lod = new XElement("Lod");
            if (min != -1)
            {
                lod.Add(new XElement("minLodPixels", min));
            }
            if (max != -1)
            {
                lod.Add(new XElement("maxLodPixels", max));
            }
            lod.Add(new XElement("minFadeExtent", ControlConstants.LodMinFadeExtent));
			lod.Add(new XElement("maxFadeExtent", ControlConstants.LodMaxFadeExtent));
            return lod;
        }

        private XElement MultiGeometry(params XElement[] geometries)
        {
            return new XElement("MultiGeometry", geometries);
        }

        private class LatLonDate
        {
            public LatLonDate(float lat, float lon, DateTime date)
            {
                this.Lat = lat;
                this.Lon = lon;
                this.Date = date;
            }

            public float Lat { get; set; }
            public float Lon { get; set; }
            public DateTime Date { get; set; }

            public override bool Equals(object obj)
            {
                if (object.ReferenceEquals(this, obj))
                    return true;

                LatLonDate other = obj as LatLonDate;
                if (other == null) return false;

                return this.Lat == other.Lat 
                    && this.Lon == other.Lon
                    && this.Date == other.Date;
            }

            public override int GetHashCode()
            {
                return 39 ^ (int)(this.Lat * 10000) ^ (int)(this.Lon * 10000) ^ (int) this.Date.Ticks;
            }
        }

    }

    public enum Visibility
    {
        Always,
        FirstLevel,
        InBetweenLevel,
        LastLevel,
    }

    public delegate string Description(CaseSet kaze, CaseSetGroup group);    

}
